package com.dynabook.ems.utils;

import cn.idev.excel.EasyExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.style.WriteCellStyle;
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
import com.dynabook.ems.common.ErrorCode;
import com.dynabook.ems.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;

/**
 * FastExcel工具类
 */
@Slf4j
public class FastExcelUtils {

    /**
     * 通用Excel导出方法（带样式）
     *
     * @param dataList 要导出的数据列表
     * @param clazz 导出数据的类型
     * @param fileName 导出文件名（不需要包含.xlsx后缀）
     * @param sheetName Excel表格名称
     * @param response HTTP响应对象
     * @param <T> 泛型，代表导出数据的类型
     */
    public static <T> void exportExcelWithStyle(List<T> dataList, Class<T> clazz, String fileName,
                                                String sheetName, HttpServletResponse response) {

        // 参数校验 - 在设置响应头之前进行
        if (dataList == null || dataList.isEmpty()) {
            log.warn("导出数据为空");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "没有可导出的数据");
        }
        if (response == null) {
            throw new IllegalArgumentException("HttpServletResponse 不能为null");
        }
        if (clazz == null) {
            throw new IllegalArgumentException("数据类型 clazz 不能为null");
        }
        if (sheetName == null || sheetName.trim().isEmpty()) {
            sheetName = "Sheet1";
            log.warn("sheetName为空，使用默认值: Sheet1");
        }

        try {
            log.info("开始导出Excel数据，数据类型：{}，文件名：{}，数据量：{}",
                    clazz.getSimpleName(), fileName, dataList.size());

            // 生成带时间戳的文件名
            String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            String fullFileName = fileName + time;

            // 重置响应状态，确保干净的响应环境
            resetResponse(response);

            // 设置响应头
            setExcelResponseHeaders(response, fullFileName);

            // 设置Excel样式
            WriteCellStyle contentStyle = new WriteCellStyle();
            contentStyle.setBorderTop(org.apache.poi.ss.usermodel.BorderStyle.THIN);
            contentStyle.setBorderBottom(org.apache.poi.ss.usermodel.BorderStyle.THIN);
            contentStyle.setBorderLeft(org.apache.poi.ss.usermodel.BorderStyle.THIN);
            contentStyle.setBorderRight(org.apache.poi.ss.usermodel.BorderStyle.THIN);
            
            // 设置表头样式，包含白色字体
            WriteCellStyle headStyle = new WriteCellStyle();
            headStyle.setFillPatternType(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
            headStyle.setFillForegroundColor((short) 40);
            
            cn.idev.excel.write.metadata.style.WriteFont headFont = new cn.idev.excel.write.metadata.style.WriteFont();
            headFont.setBold(true);
            headFont.setColor((short) 9);
            headStyle.setWriteFont(headFont);
            
            HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(
                    headStyle,
                    contentStyle
            );

            log.info("开始写入Excel文件，数据量: {}, Sheet名称: {}", dataList.size(), sheetName);

            // 导出Excel文件
            try {
                FastExcel.write(response.getOutputStream(), clazz)
                        .registerWriteHandler(styleStrategy)
                        .sheet(sheetName)
                        .doWrite(dataList);
                log.info("Excel文件写入完成");
            } catch (Exception e) {
                log.error("FastExcel写入失败，详细错误: ", e);
                throw new RuntimeException("Excel文件写入失败: " + e.getMessage(), e);
            }

            // 刷新输出流
            response.getOutputStream().flush();

            log.info("Excel数据导出完成，共导出 {} 条记录", dataList.size());

        } catch (BusinessException e) {
            log.error("导出Excel数据业务异常：{}", e.getMessage());
            handleExportException(response, e.getMessage());
        } catch (Exception e) {
            log.error("导出Excel数据失败", e);
            handleExportException(response, "导出Excel数据失败：" + e.getMessage());
        }
    }

    /**
     * 简化版通用Excel导出方法（不带样式）
     *
     * @param dataList 要导出的数据列表
     * @param clazz 导出数据的类型
     * @param fileName 导出文件名（不需要包含.xlsx后缀）
     * @param sheetName Excel表格名称
     * @param response HTTP响应对象
     * @param <T> 泛型，代表导出数据的类型
     */
    public static <T> void exportExcel(List<T> dataList, Class<T> clazz, String fileName,
                                       String sheetName, HttpServletResponse response) {

        // 参数校验 - 在设置响应头之前进行
        if (dataList == null || dataList.isEmpty()) {
            log.warn("导出数据为空");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "没有可导出的数据");
        }

        try {
            log.info("开始导出Excel数据，数据类型：{}，文件名：{}，数据量：{}",
                    clazz.getSimpleName(), fileName, dataList.size());

            // 生成带时间戳的文件名
            String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            String fullFileName = fileName + time;

            // 重置响应状态
            resetResponse(response);

            // 设置响应头
            setExcelResponseHeaders(response, fullFileName);

            // 导出Excel文件
            FastExcel.write(response.getOutputStream(), clazz)
                    .sheet(sheetName)
                    .doWrite(dataList);

            // 刷新输出流
            response.getOutputStream().flush();

            log.info("Excel数据导出完成，共导出 {} 条记录", dataList.size());

        } catch (BusinessException e) {
            log.error("导出Excel数据业务异常：{}", e.getMessage());
            handleExportException(response, e.getMessage());
        } catch (Exception e) {
            log.error("导出Excel数据失败", e);
            handleExportException(response, "导出Excel数据失败：" + e.getMessage());
        }
    }

    /**
     * 重置响应状态，防止状态污染
     */
    private static void resetResponse(HttpServletResponse response) {
        try {
            // 重置响应，清除之前可能设置的状态
            if (!response.isCommitted()) {
                response.reset();
            }
        } catch (Exception e) {
            log.warn("重置响应状态失败", e);
        }
    }

    /**
     * 设置Excel响应头
     */
    private static void setExcelResponseHeaders(HttpServletResponse response, String fileName) {
        try {
            // 设置内容类型
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");

            // 防止中文乱码
            String encodedFileName = URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8.toString())
                    .replaceAll("\\+", "%20");

            // 设置响应头
            response.setHeader("Content-Disposition",
                    "attachment; filename*=UTF-8''" + encodedFileName);
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");

            // 禁用缓存
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");

        } catch (Exception e) {
            log.error("设置响应头失败", e);
            throw new RuntimeException("设置文件下载头失败", e);
        }
    }

    /**
     * 处理导出异常
     */
    private static void handleExportException(HttpServletResponse response, String errorMessage) {
        try {
            // 如果响应还未提交，重置并返回错误信息
            if (!response.isCommitted()) {
                response.reset();
                response.setContentType("application/json");
                response.setCharacterEncoding("UTF-8");
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

                // 返回简单的错误JSON
                String errorJson = "{\"code\":500,\"message\":\"" + errorMessage + "\",\"data\":null}";
                response.getWriter().write(errorJson);
                response.getWriter().flush();
            }
        } catch (Exception e) {
            log.error("处理导出异常时发生错误", e);
        }
    }


    /**
     * 设置响应头
     * @deprecated 使用 setExcelResponseHeaders 替代
     */
    @Deprecated
    private static void setResponseHeaders(HttpServletResponse response, String fileName) {
        setExcelResponseHeaders(response, fileName);
    }

    /**
     * 生成导出文件名
     */
    public static String generateExportFileName(String prefix) {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        return prefix + "_" + timestamp;
    }

    /**
     * 生成导出文件名（带数据量）
     */
    public static String generateExportFileName(String prefix, int dataCount) {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        return prefix + "_" + dataCount + "条_" + timestamp;
    }

    /**
     * 盘点数据专用导出方法（性能优化版，支持多sheet）
     * 专门为大数据量导出优化，减少样式处理和日志开销，支持导出多个sheet
     *
     * @param dataList 要导出的数据列表
     * @param clazz 导出数据的类型
     * @param fileName 导出文件名（不需要包含.xlsx后缀）
     * @param sheetName Excel表格名称
     * @param response HTTP响应对象
     * @param <T> 泛型，代表导出数据的类型
     */
    public static <T> void exportInventoryExcel(List<T> dataList, Class<T> clazz, String fileName,
                                                String sheetName, HttpServletResponse response) {
        // 参数校验
        if (dataList == null || dataList.isEmpty()) {
            log.warn("导出数据为空");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "没有可导出的数据");
        }
        if (response == null) {
            throw new IllegalArgumentException("HttpServletResponse 不能为null");
        }
        if (clazz == null) {
            throw new IllegalArgumentException("数据类型 clazz 不能为null");
        }
        if (sheetName == null || sheetName.trim().isEmpty()) {
            sheetName = "Sheet1";
        }

        try {
            // 简化日志输出，减少IO开销
            log.info("开始导出盘点Excel: {}, 数据量: {}", fileName, dataList.size());

            // 生成带时间戳的文件名
            String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            String fullFileName = fileName + time;

            // 重置响应状态
            resetResponse(response);

            // 设置响应头
            setExcelResponseHeaders(response, fullFileName);

            // 创建优化的样式策略
            HorizontalCellStyleStrategy styleStrategy = createOptimizedStyleStrategy();

            // 导出Excel文件
            try {
                FastExcel.write(response.getOutputStream(), clazz)
                        .registerWriteHandler(styleStrategy)
                        .sheet(sheetName)
                        .doWrite(dataList);
            } catch (Exception e) {
                log.error("盘点数据Excel写入失败: {}", e.getMessage());
                throw new RuntimeException("Excel文件写入失败: " + e.getMessage(), e);
            }

            // 刷新输出流
            response.getOutputStream().flush();
            
            log.info("盘点Excel导出完成: {} 条", dataList.size());

        } catch (BusinessException e) {
            log.error("导出盘点Excel业务异常：{}", e.getMessage());
            handleExportException(response, e.getMessage());
        } catch (Exception e) {
            log.error("导出盘点Excel失败", e);
            handleExportException(response, "导出盘点Excel失败：" + e.getMessage());
        }
    }

    /**
     * 盘点数据多Sheet导出方法（性能优化版）
     * 支持在一个Excel文件中导出多个sheet
     *
     * @param dataLists 多个sheet的数据列表
     * @param clazz 导出数据的类型
     * @param fileName 导出文件名（不需要包含.xlsx后缀）
     * @param sheetNames sheet名称列表
     * @param response HTTP响应对象
     * @param <T> 泛型，代表导出数据的类型
     */
    public static <T> void exportInventoryMultiSheetExcel(List<List<T>> dataLists, Class<T> clazz, String fileName,
                                                          List<String> sheetNames, HttpServletResponse response) {
        // 参数校验
        if (dataLists == null || dataLists.isEmpty()) {
            log.warn("导出数据为空");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "没有可导出的数据");
        }
        if (sheetNames == null || sheetNames.size() != dataLists.size()) {
            throw new IllegalArgumentException("sheet名称数量必须与数据列表数量一致");
        }
        if (response == null) {
            throw new IllegalArgumentException("HttpServletResponse 不能为null");
        }
        if (clazz == null) {
            throw new IllegalArgumentException("数据类型 clazz 不能为null");
        }

        try {
            // 计算总数据量
            int totalCount = dataLists.stream().mapToInt(List::size).sum();
            log.info("开始导出多Sheet盘点Excel: {}, Sheet数: {}, 总数据量: {}", 
                fileName, sheetNames.size(), totalCount);

            // 生成带时间戳的文件名
            String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            String fullFileName = fileName + time;

            // 重置响应状态
            resetResponse(response);

            // 设置响应头
            setExcelResponseHeaders(response, fullFileName);

            // 创建优化的样式策略
            HorizontalCellStyleStrategy styleStrategy = createOptimizedStyleStrategy();

            // 使用FastExcel链式调用写入多个sheet
            try {
                // 创建第一个sheet
                cn.idev.excel.write.builder.ExcelWriterBuilder writerBuilder = 
                    FastExcel.write(response.getOutputStream(), clazz)
                            .registerWriteHandler(styleStrategy);
                
                // 写入第一个sheet
                if (!dataLists.isEmpty()) {
                    writerBuilder.sheet(0, sheetNames.get(0)).doWrite(dataLists.get(0));
                    log.debug("Sheet [{}] 写入完成: {} 条", sheetNames.get(0), dataLists.get(0).size());
                }
                
                // 写入后续的sheet
                for (int i = 1; i < dataLists.size(); i++) {
                    List<T> data = dataLists.get(i);
                    String sheetName = sheetNames.get(i);
                    
                    writerBuilder.sheet(i, sheetName).doWrite(data);
                    
                    log.debug("Sheet [{}] 写入完成: {} 条", sheetName, data.size());
                }
                
            } catch (Exception e) {
                log.error("多Sheet盘点Excel写入失败: {}", e.getMessage());
                throw new RuntimeException("Excel文件写入失败: " + e.getMessage(), e);
            }

            // 刷新输出流
            response.getOutputStream().flush();
            
            log.info("多Sheet盘点Excel导出完成: {} 个Sheet, 共 {} 条记录", sheetNames.size(), totalCount);

        } catch (BusinessException e) {
            log.error("导出多Sheet盘点Excel业务异常：{}", e.getMessage());
            handleExportException(response, e.getMessage());
        } catch (Exception e) {
            log.error("导出多Sheet盘点Excel失败", e);
            handleExportException(response, "导出多Sheet盘点Excel失败：" + e.getMessage());
        }
    }

    /**
     * 创建性能优化的样式策略
     * 只设置表头样式，不设置单元格边框，提升写入性能
     *
     * @return 样式策略
     */
    private static HorizontalCellStyleStrategy createOptimizedStyleStrategy() {
        // 表头样式
        WriteCellStyle headStyle = new WriteCellStyle();
        headStyle.setFillPatternType(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
        headStyle.setFillForegroundColor((short) 40);
        
        // 设置表头对齐方式
        headStyle.setHorizontalAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.CENTER);
        headStyle.setVerticalAlignment(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER);
        
        cn.idev.excel.write.metadata.style.WriteFont headFont = new cn.idev.excel.write.metadata.style.WriteFont();
        headFont.setBold(true);
        headFont.setColor((short) 9);
        headFont.setFontHeightInPoints((short) 12); // 设置表头字体大小
        headStyle.setWriteFont(headFont);
        
        // 内容样式使用空样式，不设置边框，大幅提升写入速度
        WriteCellStyle contentStyle = new WriteCellStyle();
        
        return new HorizontalCellStyleStrategy(headStyle, contentStyle);
    }
    
    /**
     * 支持不同类型数据的多Sheet导出（性能优化版）
     * 每个sheet可以有不同的数据类型
     *
     * @param dataLists 多个sheet的数据列表
     * @param classes 每个sheet对应的数据类型
     * @param fileName 导出文件名（不需要包含.xlsx后缀）
     * @param sheetNames sheet名称列表
     * @param response HTTP响应对象
     */
    public static void exportMultiTypeSheetExcel(List<List<?>> dataLists, List<Class<?>> classes, 
                                                  String fileName, List<String> sheetNames, 
                                                  HttpServletResponse response) {
        // 参数校验
        if (dataLists == null || dataLists.isEmpty()) {
            log.warn("导出数据为空");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "没有可导出的数据");
        }
        if (sheetNames == null || sheetNames.size() != dataLists.size()) {
            throw new IllegalArgumentException("sheet名称数量必须与数据列表数量一致");
        }
        if (classes == null || classes.size() != dataLists.size()) {
            throw new IllegalArgumentException("数据类型数量必须与数据列表数量一致");
        }
        if (response == null) {
            throw new IllegalArgumentException("HttpServletResponse 不能为null");
        }

        try {
            // 计算总数据量
            int totalCount = dataLists.stream().mapToInt(List::size).sum();
            log.info("开始导出多类型Sheet Excel: {}, Sheet数: {}, 总数据量: {}", 
                fileName, sheetNames.size(), totalCount);

            // 生成带时间戳的文件名
            String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            String fullFileName = fileName + time;

            // 重置响应状态
            resetResponse(response);

            // 设置响应头
            setExcelResponseHeaders(response, fullFileName);

            // 创建优化的样式策略
            HorizontalCellStyleStrategy styleStrategy = createOptimizedStyleStrategy();

            // 使用EasyExcel API写入多个不同类型的sheet
            ExcelWriter excelWriter = null;
            try {
                // 创建ExcelWriter对象（使用EasyExcel的API）
                excelWriter = EasyExcel.write(response.getOutputStream())
                        .registerWriteHandler(styleStrategy)
                        .build();
                
                // 写入每个sheet
                for (int i = 0; i < dataLists.size(); i++) {
                    List<?> data = dataLists.get(i);
                    String sheetName = sheetNames.get(i);
                    Class<?> clazz = classes.get(i);
                    
                    // 创建WriteSheet对象，为每个sheet指定不同的head类型
                    WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName)
                            .head(clazz)
                            .build();
                    
                    // 写入数据
                    excelWriter.write(data, writeSheet);
                    
                    log.debug("Sheet [{}] 写入完成: {} 条", sheetName, data.size());
                }
                
            } catch (Exception e) {
                log.error("多类型Sheet Excel写入失败: {}", e.getMessage(), e);
                throw new RuntimeException("Excel文件写入失败: " + e.getMessage(), e);
            } finally {
                // 关闭ExcelWriter
                if (excelWriter != null) {
                    excelWriter.finish();
                }
            }

            // 刷新输出流
            response.getOutputStream().flush();
            
            log.info("多类型Sheet Excel导出完成: {} 个Sheet, 共 {} 条记录", sheetNames.size(), totalCount);

        } catch (BusinessException e) {
            log.error("导出多类型Sheet Excel业务异常：{}", e.getMessage());
            handleExportException(response, e.getMessage());
        } catch (Exception e) {
            log.error("导出多类型Sheet Excel失败", e);
            handleExportException(response, "导出多类型Sheet Excel失败：" + e.getMessage());
        }
    }
}